/*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xwiki.gwt.wysiwyg.client.plugin.link.ui;
import org.xwiki.gwt.user.client.FocusCommand;
import org.xwiki.gwt.user.client.StringUtils;
import org.xwiki.gwt.user.client.ui.wizard.AbstractInteractiveWizardStep;
import org.xwiki.gwt.user.client.ui.wizard.NavigationListener;
import org.xwiki.gwt.user.client.ui.wizard.NavigationListener.NavigationDirection;
import org.xwiki.gwt.user.client.ui.wizard.NavigationListenerCollection;
import org.xwiki.gwt.user.client.ui.wizard.SourcesNavigationEvents;
import org.xwiki.gwt.wysiwyg.client.Strings;
import org.xwiki.gwt.wysiwyg.client.plugin.link.LinkConfig;
import org.xwiki.gwt.wysiwyg.client.wiki.AttachmentReference;
import org.xwiki.gwt.wysiwyg.client.wiki.EntityLink;
import org.xwiki.gwt.wysiwyg.client.wiki.ResourceReference;
import org.xwiki.gwt.wysiwyg.client.wiki.ResourceReference.ResourceType;
import org.xwiki.gwt.wysiwyg.client.wiki.WikiServiceAsync;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.InlineLabel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.TextBox;
/**
* Default implementation for the link configuration parameters, such as link labels, link tooltip, or opening the link
* in a new window or not.
*
* @version $Id: a79cac0418a7993d44d1b773b5c83d5bbae1fd7a $
*/
public class LinkConfigWizardStep extends AbstractInteractiveWizardStep implements SourcesNavigationEvents,
KeyPressHandler
{
/**
* The default style of the link configuration dialog.
*/
public static final String DEFAULT_STYLE_NAME = "xLinkConfig";
/**
* The style of the information labels in this form.
*/
public static final String INFO_LABEL_STYLE = "xInfoLabel";
/**
* The style of the description labels in this form.
*/
public static final String HELP_LABEL_STYLE = "xHelpLabel";
/**
* The style of the error labels in this form.
*/
public static final String ERROR_LABEL_STYLE = "xErrorMsg";
/**
* The style of the fields under error.
*/
protected static final String FIELD_ERROR_STYLE = "xErrorField";
/**
* The entity link managed by the wizard step. This wizard step updates the configuration object attached to the
* entity link.
*/
private EntityLink<LinkConfig> entityLink;
/**
* Collection of {@link NavigationListener}s, to be notified by navigation events from this step. Used to handle
* default buttons in this wizard step, by firing next event whenever enter key is hit in the step form.
*/
private final NavigationListenerCollection navigationListeners = new NavigationListenerCollection();
/**
* The text box where the user will insert the text of the link to create.
*/
private final TextBox labelTextBox = new TextBox();
/**
* The label to signal the error on the label field of this form.
*/
private final Label labelErrorLabel = new Label();
/**
* The text box to get the link tooltip.
*/
private final TextBox tooltipTextBox = new TextBox();
/**
* The checkbox to query about whether the link should be opened in a new window or not.
*/
private CheckBox newWindowCheckBox;
/**
* The service used to parse the image reference when the link label is an image.
*/
private final WikiServiceAsync wikiService;
/**
* Creates a new link configuration wizard step.
*
* @param wikiService the service to be used for parsing the image reference in case the link label is an image
*/
public LinkConfigWizardStep(WikiServiceAsync wikiService)
{
this.wikiService = wikiService;
setStepTitle(Strings.INSTANCE.linkConfigTitle());
display().addStyleName(DEFAULT_STYLE_NAME);
setUpLabelField();
Label tooltipLabel = new Label(Strings.INSTANCE.linkTooltipLabel());
tooltipLabel.setStyleName(INFO_LABEL_STYLE);
Label helpTooltipLabel = new Label(getTooltipTextBoxTooltip());
helpTooltipLabel.setStyleName(HELP_LABEL_STYLE);
// on enter in the textbox, submit the form
tooltipTextBox.addKeyPressHandler(this);
tooltipTextBox.setTitle(getTooltipTextBoxTooltip());
display().add(tooltipLabel);
display().add(helpTooltipLabel);
display().add(tooltipTextBox);
newWindowCheckBox = new CheckBox(Strings.INSTANCE.linkOpenInNewWindowLabel());
// just add the style, because we need to be able to still detect this is a checkbox
newWindowCheckBox.addStyleName(INFO_LABEL_STYLE);
Label helpNewWindowLabel = new Label(Strings.INSTANCE.linkOpenInNewWindowHelpLabel());
helpNewWindowLabel.setStyleName(HELP_LABEL_STYLE);
display().add(newWindowCheckBox);
display().add(helpNewWindowLabel);
}
/**
* Helper function to setup the label field in this link form.
*/
private void setUpLabelField()
{
Panel labelLabel = new FlowPanel();
labelLabel.setStyleName(INFO_LABEL_STYLE);
labelLabel.add(new InlineLabel(Strings.INSTANCE.linkLabelLabel()));
InlineLabel mandatoryLabel = new InlineLabel(Strings.INSTANCE.mandatory());
mandatoryLabel.addStyleName("xMandatory");
labelLabel.add(mandatoryLabel);
Label helpLabelLabel = new Label(getLabelTextBoxTooltip());
helpLabelLabel.setStyleName(HELP_LABEL_STYLE);
labelErrorLabel.addStyleName(ERROR_LABEL_STYLE);
labelErrorLabel.setVisible(false);
// on enter in the textbox, submit the form
labelTextBox.addKeyPressHandler(this);
labelTextBox.setTitle(getLabelTextBoxTooltip());
display().add(labelLabel);
display().add(helpLabelLabel);
display().add(labelErrorLabel);
display().add(labelTextBox);
}
@Override
@SuppressWarnings("unchecked")
public void init(Object data, final AsyncCallback< ? > callback)
{
entityLink = (EntityLink<LinkConfig>) data;
LinkConfig linkConfig = entityLink.getData();
if (linkConfig.isReadOnlyLabel()) {
wikiService.parseLinkReference(linkConfig.getLabelText(), entityLink.getOrigin(),
new AsyncCallback<ResourceReference>()
{
@Override
public void onFailure(Throwable caught)
{
callback.onFailure(caught);
}
@Override
public void onSuccess(ResourceReference result)
{
init(result, callback);
}
});
} else {
init(entityLink.getDestination(), callback);
}
}
/**
* Initializes the wizard step based on the underlying link configuration object. If the link label is an image the
* UI is adjusted accordingly (the image name is displayed as the link label and the link label is read-only). If
* the link target is an attachment we use the attachment name as the link label.
*
* @param labelResourceReference a reference to the resource specified by the link label
* @param callback the object to be notified after the wizard step has been initialized
*/
private void init(ResourceReference labelResourceReference, AsyncCallback< ? > callback)
{
LinkConfig linkConfig = entityLink.getData();
boolean useFileName =
labelResourceReference.getType() == ResourceType.ATTACHMENT
&& (linkConfig.isReadOnlyLabel() || StringUtils.isEmpty(linkConfig.getLabel()));
labelTextBox.setText(useFileName ? new AttachmentReference(labelResourceReference.getEntityReference())
.getFileName() : linkConfig.getLabelText());
labelTextBox.setEnabled(!linkConfig.isReadOnlyLabel());
tooltipTextBox.setText(linkConfig.getTooltip() == null ? "" : linkConfig.getTooltip());
newWindowCheckBox.setValue(linkConfig.isOpenInNewWindow());
hideErrors();
callback.onSuccess(null);
setFocus();
}
/**
* Sets the default focus in this wizard step.
*/
protected void setFocus()
{
Scheduler.get().scheduleDeferred(new FocusCommand(labelTextBox.isEnabled() ? labelTextBox : tooltipTextBox));
}
/**
* @return the labelTextBox
*/
protected TextBox getLabelTextBox()
{
return labelTextBox;
}
/**
* @return the tooltip for label text box
*/
protected String getLabelTextBoxTooltip()
{
return Strings.INSTANCE.linkConfigLabelTextBoxTooltip();
}
/**
* @return the tooltip for the tooltip text box
*/
protected String getTooltipTextBoxTooltip()
{
return Strings.INSTANCE.linkConfigTooltipTextBoxTooltip();
}
/**
* @return the tooltipTextBox
*/
public TextBox getTooltipTextBox()
{
return tooltipTextBox;
}
/**
* @return the newWindowCheckBox
*/
public CheckBox getNewWindowCheckBox()
{
return newWindowCheckBox;
}
@Override
public void onSubmit(AsyncCallback<Boolean> async)
{
// first reset all error labels, consider everything's fine
hideErrors();
// validate and save if everything's fine
if (!validateForm()) {
async.onSuccess(false);
// and set the focus
setFocus();
} else {
saveForm(async);
}
}
/**
* Validates this step's form and displays errors if needed.
*
* @return {@code true} if the form is valid and data can be saved, {@code false} otherwise.
*/
protected boolean validateForm()
{
if (labelTextBox.getText().trim().length() == 0) {
displayLabelError(Strings.INSTANCE.linkNoLabelError());
return false;
}
return true;
}
/**
* Saves the form values in this step's data, to be called only when {@link #validateForm()} returns {@code true}.
*
* @param callback the object to be notified after the form is saved
*/
protected void saveForm(AsyncCallback<Boolean> callback)
{
LinkConfig linkConfig = entityLink.getData();
if (!linkConfig.isReadOnlyLabel() && !labelTextBox.getText().trim().equals(linkConfig.getLabelText().trim())) {
linkConfig.setLabel(labelTextBox.getText().trim());
linkConfig.setLabelText(labelTextBox.getText().trim());
}
linkConfig.setTooltip(getTooltipTextBox().getText());
linkConfig.setOpenInNewWindow(getNewWindowCheckBox().getValue());
callback.onSuccess(true);
}
@Override
public void onCancel()
{
}
@Override
public Object getResult()
{
// Always return the modified entity link as result of this wizard step.
return entityLink;
}
/**
* @return the default navigation direction, to be fired automatically when enter is hit in an input in the form of
* this configuration wizard step. To be overridden by subclasses to provide the specific direction to be
* followed.
*/
public NavigationDirection getDefaultDirection()
{
return NavigationDirection.FINISH;
}
@Override
public void addNavigationListener(NavigationListener listener)
{
navigationListeners.add(listener);
}
@Override
public void removeNavigationListener(NavigationListener listener)
{
navigationListeners.remove(listener);
}
@Override
public void onKeyPress(KeyPressEvent event)
{
if (event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER) {
// fire the event for the default direction
navigationListeners.fireNavigationEvent(getDefaultDirection());
}
}
/**
* Display the label error message and markers.
*
* @param errorMessage the error message to display.
*/
protected void displayLabelError(String errorMessage)
{
labelErrorLabel.setText(errorMessage);
labelErrorLabel.setVisible(true);
labelTextBox.addStyleName(FIELD_ERROR_STYLE);
}
/**
* Hides the error message and markers for this dialog.
*/
protected void hideErrors()
{
labelErrorLabel.setVisible(false);
labelTextBox.removeStyleName(FIELD_ERROR_STYLE);
}
/**
* @return the labelErrorLabel
*/
protected Label getLabelErrorLabel()
{
return labelErrorLabel;
}
/**
* @return the data configured by this wizard step
*/
protected EntityLink<LinkConfig> getData()
{
return entityLink;
}
/**
* @return the service used to parse and serialize entity/resource references
*/
protected WikiServiceAsync getWikiService()
{
return wikiService;
}
}